__author__ = 'Brandon Corfman'
import term, help
from agent import Agent
from problem import FindLadderProblem
from search import dfs
from path import Goal, Ladder, Path

class HumanAgent(Agent):
    def display_hand(self):
        self.parent.display_name()
        if self.states:
            states = "Broken tools: "
            for i in range(len(self.states)-1):
                states += self.states[i] + ", "
            states += self.states[-1]
            print(states)
        print("Cards in hand:")
        for i, card in enumerate(self.hand):
            print("{0}. {1}".format(i+1, card))
        print()

    def receive(self, msg):
        pass

    def add_card_to_hand(self, card):
        self.hand.append(card)

    def get_card_choice(self):
        while True:
            if not self.hand:
                return None, 0, None
            move = None
            while move not in ['p', 'd', 'f', 'w', 'h']: # play, discard, flip, who, or help
                move = input('(P)lay, (D)iscard, (F)lip a card, (W)ho am I, or (H)elp? ').strip().lower()

            card_list = None
            if move == 'p' or move == 'd':
                card_list = range(1, len(self.hand)+1)
            elif move == 'w':
                self.parent.identify_player_type()
                continue
            elif move == 'f':
                card_list = [i+1 for i, card in enumerate(self.hand) if card.is_flippable()]
            elif move == 'h':
                help.display()
                term.clear()
                print(self.parent.game.board)
                self.display_hand()
                continue
            i = 0
            while i not in card_list:
                try:
                    entry = input('Which card number? ')
                    if entry == '':
                        move = None
                        break
                    i = int(entry)
                    if i not in card_list:
                        print("That card can't be flipped. Try again.")
                        move = None
                        break
                except ValueError:
                    pass
            if move == 'f':
                self.hand[i-1].spin()
                self.display_hand()
            elif move == 'p' or move == 'd':
                return move, i-1, self.hand[i-1]
            
    def play_card(self, board, others):
        """
        Let the user pick and play a card from those in their hand.
        """
        term.clear()
        print(board)
        self.display_hand()
        while True:
            action, index, card = self.get_card_choice()
            if action == 'p': # play
                if card.precondition(self, board, others):
                    card.execute(self, board, others)
                else:
                    continue
            if action == 'p' or action == 'd': # play or discard
                self.hand.pop(index)
            return action

    def validate(self, row, col, card, board):
        """
        Returns True if card can be placed at a location on the board; False otherwise.
        row,col: Board coordinates where card should be placed
        """
        path = board.path
        if path[row][col]:
            return False # sorry, another card is already placed there.

        path_n = (path[row-1][col] is not None and path[row-1][col].exit.find('S') >= 0 and
                  card.enter.find('N') >= 0)
        no_path_n = (path[row-1][col] is None or isinstance(path[row-1][col], Goal) or
                     (path[row-1][col] is not None and
                      path[row-1][col].exit.find('S') == -1 and card.enter.find('N') == -1))

        path_s = (path[row+1][col] is not None and path[row+1][col].exit.find('N') >= 0 and
                  card.enter.find('S') >= 0)
        no_path_s = (path[row+1][col] is None or isinstance(path[row+1][col], Goal) or
                     (path[row+1][col] is not None and
                      path[row+1][col].exit.find('N') == -1 and card.enter.find('S') == -1))

        path_w = (path[row][col-1] is not None and path[row][col-1].exit.find('E') >= 0 and
                  card.enter.find('W') >= 0)
        no_path_w = (path[row][col-1] is None or isinstance(path[row][col-1], Goal) or
                     (path[row][col-1] is not None and
                      path[row][col-1].exit.find('E') == -1 and card.enter.find('W') == -1))

        path_e = (path[row][col+1] is not None and path[row][col+1].exit.find('W') >= 0 and
                  card.enter.find('E') >= 0)
        no_path_e = (path[row][col+1] is None or isinstance(path[row][col+1], Goal) or
                     (path[row][col+1] is not None and
                      path[row][col+1].exit.find('W') == -1 and card.enter.find('E') == -1))

        if no_path_n and no_path_s and no_path_w and no_path_e:
            return False

        path_to_start = dfs(FindLadderProblem(card, row, col, board))
        return ((path_n or no_path_n) and (path_s or no_path_s) and (path_w or no_path_w) and
                (path_e or no_path_e) and path_to_start)
    
    def reveal_goal(self, board, pos):
        row, col = board.goal[pos]
        state = board.path[row][col].state
        item = {0: 'first', 1: 'second', 2: 'third'}
        if state == 'gold':
            print('\nThe {0} goal card has {1}!\n'.format(item[pos], state))
        elif state == 'stone':
            print('\nSorry! The {0} goal card has a {1}!\n'.format(item[pos], state))
        term.wait_for_enter_key()

    def decide_path_card(self, board, card):
        """
        This method queries the user on where the chosen path card
        should be placed on the board (row,col location) and checks to see
        whether the selection fits in with the other surrounding cards.
        path: the current path on the board
        card: the path card picked by the user
        """
        rank = 0.0
        row, col = None, None
        while 1:
            try:
                entry = input('Place the card where on the board (row,col)? ')
                if entry == '':
                    # typing nothing at the prompt will cause the decision to fail and
                    # return the user back to the play_card loop.
                    return rank, row, col
                r, c = entry.split(',')
                r, c = int(r), int(c)
                if self.validate(r, c, card, board):
                    rank = 1.0
                    row, col = r, c
                    break
                else:
                    print("Selected card doesn't work with the spot you've chosen. Try again.")
            except ValueError:
                pass
        return rank, row, col

    def decide_discard(self, board, card):
        return 1.0

    def decide_rockfall(self, board, card):
        rank = 0.0
        row, col = None, None
        while 1:
            try:
                entry = input('Remove which location (row, col)? ').strip()
                if entry == '':
                    # typing nothing at the prompt will cause the decision to fail and
                    # return the user back to the play_card loop.
                    return rank, row, col
                r, c = entry.split(',')
                r, c = int(r), int(c)
                loc = board.path[r][c]
                if isinstance(loc, Ladder) or isinstance(loc, Goal):
                    print("Sorry, you cannot remove a Start or Goal location. Try again.")
                elif isinstance(loc, Path):
                    rank = 1.0
                    row, col = r, c
                    break
                else:
                    print("The location you've chosen is empty. Try again.")
            except ValueError:
                pass
        return rank, row, col

    def decide_peek(self, board, card):
        rank = 0.0
        n = 0
        while 1:
            try:
                entry = input('Which goal card do you want to look at (1-3)? ')
                if entry == '':
                    # typing nothing at the prompt will cause the decision to fail and
                    # return the user back to the play_card loop.
                    return rank, None
                n = int(entry)
                if n >= 1 or n <= 3:
                    rank = 1.0
                    break
            except ValueError:
                pass
        return rank, n-1

    def decide_repair_one(self, board, card, others):
        # build list of all relevant_players
        all_players = [self.parent]
        all_players.extend(others)
        relevant_players = [p for p in all_players if (card.state in p.get_states())]
        idx = 1
        if len(relevant_players) > 1:
            for i, p in enumerate(relevant_players):
                print('{0}. {1}'.format(i+1, str(p)))
            while 1:
                try:
                    entry = input('Which player (1-{0})? '.format(len(relevant_players)))
                    if entry == '':
                        # typing nothing at the prompt will cause the decision to fail and
                        # return the user back to the play_card loop.
                        return 0.0, None
                    idx = int(entry)
                    if idx >= 1 or idx <= len(others):
                        break
                except ValueError:
                    pass
        return 1.0, relevant_players[idx-1], card.state

    def display_repair_one_precond_failure(self, state):
        print("There aren't any players with broken {0}s!\n".format(state))

    def decide_repair_any(self, board, card, others):
        rank = 0.0
        # build list of all relevant_players
        all_players = [self.parent]
        all_players.extend(others)
        relevant_players = [p for p in all_players if (card.state1 in p.get_states() or
                                                       card.state2 in p.get_states())]
        idx = 1
        if len(relevant_players) > 1:
            for i, p in enumerate(relevant_players):
                print('{0}. {1}'.format(i+1, str(p)))
            while 1:
                try:
                    entry = input('Which player (1-{0})? '.format(len(relevant_players)))
                    if entry == '':
                        # typing nothing at the prompt will cause the decision to fail and
                        # return the user back to the play_card loop.
                        return 0.0, None, None
                    idx = int(entry)
                    if idx >= 1 or idx <= len(others):
                        break
                except ValueError:
                    pass

        # if we can figure out which state needs to be repaired without asking the user,
        # do it automatically.
        states = relevant_players[idx-1].get_states()
        if card.state1 not in states and card.state2 in states:
            return 1.0, relevant_players[idx-1], card.state2
        if card.state1 in states and card.state2 not in states:
            return 1.0, relevant_players[idx-1], card.state1

        # if we get here, the user has both tools broken, so they must select which tool
        # they want to repair.
        state = None
        while 1:
            try:
                state = input('Repair ({0}){1} or ({2}){3}? '.format(card.state1[0], card.state1[1:],
                                                                     card.state2[0], card.state2[1:]))
                if state == '':
                    # typing nothing at the prompt will cause the decision to fail and
                    # return the user back to the play_card loop.
                    return 0.0, None, None
                if state[0] == card.state1[0]:
                    state = card.state1
                    rank = 1.0
                    break
                elif state[0] == card.state2[0]:
                    state = card.state2
                    rank = 1.0
                    break
                else:
                    print("You can't repair that item. Try again.")
            except ValueError:
                pass
        return rank, relevant_players[idx-1], state

    def display_repair_any_precond_failure(self, state1, state2):
        print("There aren't any players with broken {0}s or {1}s!\n".format(state1, state2))

    def decide_broken_tool(self, board, state, others):
        rank = 0.0
        relevant_players = [o for o in others if state not in o.get_states()]
        n = 1
        if len(relevant_players) == 1:
            rank = 1.0
        else:
            for i, p in enumerate(relevant_players):
                print('{0}. {1}'.format(i+1, str(p)))
            while 1:
                try:
                    entry = input('Which player (1-{0})? '.format(len(others)))
                    if entry == '':
                        # typing nothing at the prompt will cause the decision to fail and
                        # return the user back to the play_card loop.
                        return 0.0, None
                    n = int(entry)
                    if 1 <= n <= len(relevant_players):
                        if state not in relevant_players[n-1].get_states():
                            rank = 1.0
                            break
                        else:
                            print("That player already has a broken {0}. Try again.".format(state))
                    else:
                        print("That's not a valid number. Try again.")
                except ValueError:
                    pass
        return rank, relevant_players[n-1]
